﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.IO;
using System.Globalization;

namespace DNA
{
    class Program
    {
        public static bool batch = false;
        [DllImport("kernel32.dll")]
        extern static short QueryPerformanceCounter(ref long x);
        [DllImport("kernel32.dll")]
        extern static short QueryPerformanceFrequency(ref long x);
        
        static void Main(string[] args)
        {
            bool doBeep = true;
            bool wholeSpectrum = false;

            bool positiveOnly = false;
            bool negativeOnly = false;

            long startTime = 0, stopTime = 0;
            long freq = 0;
            QueryPerformanceFrequency(ref freq);

            int iteration_mult = 2;
            int ants_quant = 40;
            int ranks_quant = 8;

            double feromoneVaporRate = 0.9;
            double heatVaporRate = 0.5;
            double startHeat = 0.0;
            double feromoneToDistribute = 15.0;

            Console.Title = "DNA Sequencing using Ant System with Ranks (c) Urban & Ziętkiewicz";

            List<string> argsStr = new List<string>(args);

            int args_len = argsStr.Count;
            for (int i = 0; i < args_len; i++ )
            {
                string arg = args[i];
                if (arg == "-b")
                {
                    doBeep = false;
                    argsStr.Remove(arg);
                }
                if (arg == "-e=p")
                {
                    positiveOnly = true;
                    negativeOnly = false;
                    argsStr.Remove(arg);
                }
                if (arg == "-e=n")
                {
                    positiveOnly = false;
                    negativeOnly = true;
                    argsStr.Remove(arg);
                }
                if (arg == "-h")
                {
                    Console.WriteLine("DNA <path_to_dna_data> <max_sequence_length> [options], where options are:\n\t-b : to remove beep after finding solution,\n\t-e=p : only positive errors,\n\t-e=n : only negative errors,\n\t-h : this message.");
                    return;
                }
                if (arg.StartsWith("xi="))
                {
                    try
                    {
                        iteration_mult = int.Parse(arg.Substring("xi=".Length));
                    }
                    catch (Exception)
                    {
                        Console.Error.WriteLine("Value for iteration multiplier is not an int.");
                        return;
                    }
                    argsStr.Remove(arg);
                }
                if (arg.StartsWith("fvapor="))
                {
                    try
                    {
                        feromoneVaporRate = double.Parse(arg.Substring("fvapor=".Length), CultureInfo.InvariantCulture);
                    }
                    catch (Exception e)
                    {
                        Console.Error.WriteLine("Value for feromone vapor rate is not a double.\n{0}",e.Message);
                        return;
                    }
                    argsStr.Remove(arg);
                }
                if (arg.StartsWith("hvapor="))
                {
                    try
                    {
                        heatVaporRate = double.Parse(arg.Substring("hvapor=".Length), CultureInfo.InvariantCulture);
                    }
                    catch (Exception e)
                    {
                        Console.Error.WriteLine("Value for heat vapor rate is not a double.\n{0}",e.Message);
                        return;
                    }
                    argsStr.Remove(arg);
                }
                if (arg.StartsWith("heat="))
                {
                    try
                    {
                        startHeat = double.Parse(arg.Substring("heat=".Length), CultureInfo.InvariantCulture);
                    }
                    catch (Exception e)
                    {
                        Console.Error.WriteLine("Value for starting heat is not a double.\n{0}", e.Message);
                        return;
                    }
                    argsStr.Remove(arg);
                }
                if (arg.StartsWith("ftd="))
                {
                    try
                    {
                        feromoneToDistribute = double.Parse(arg.Substring("ftd=".Length), CultureInfo.InvariantCulture);
                    }
                    catch (Exception e)
                    {
                        Console.Error.WriteLine("Value for feromone to distribute is not a double.\n{0}", e.Message);
                        return;
                    }
                    argsStr.Remove(arg);
                }
                if (arg.StartsWith("ants="))
                {
                    try
                    {
                        ants_quant = int.Parse(arg.Substring("ants=".Length));
                    }
                    catch (Exception)
                    {
                        Console.Error.WriteLine("Number of ants is not an int.");
                        return;
                    }
                    argsStr.Remove(arg);
                }
                if (arg.StartsWith("ranks="))
                {
                    try
                    {
                        ranks_quant = int.Parse(arg.Substring("ranks=".Length));
                    }
                    catch (Exception)
                    {
                        Console.Error.WriteLine("Number of ranks is not an int.");
                        return;
                    }
                    argsStr.Remove(arg);
                }
                if (arg == "-i")
                {
                    batch = true;
                    argsStr.Remove(arg);
                }
                if (arg == "-all")
                {
                    wholeSpectrum = true;
                    argsStr.Remove(arg);
                }
            }

            if (ranks_quant > ants_quant)
            {
                Console.Error.WriteLine("Number of ranks is greater than number of ants.");
                return;
            }

            if (argsStr.Count != 2)
            {
                Console.Error.WriteLine("Incorrect parameters use. \"-h\" for help.");
                foreach (var item in argsStr)
                {
                    Console.Error.WriteLine(item);
                }
                return;
            }

            string filename = argsStr[0];
            int seqLen;

            if((!int.TryParse(argsStr[1],out seqLen)) || seqLen <= 0)
            {
                Console.Error.WriteLine("Incorrect value for sequence length.");
                return;
            }

            QueryPerformanceCounter(ref startTime);
            
            DnaIndex index;
            try
            {
                string data = File.ReadAllText(filename);
                index = new DnaIndex(data);
            }
            catch (IOException)
            {
                Console.Error.WriteLine("Cannot open file: " + filename);
                return;
            }
            catch (Exception e)
            {
                Console.Error.WriteLine(e.Message);
                return;
            }

            List<string> spectrum = index.getAll();
            if (spectrum.Count == 0)
            {
                Console.Error.WriteLine("Data in file invalid: "+filename);
            }
            int partLen = spectrum[0].Length;

            //Zawężamy zbiór startowych nukleotydów
            List<string> merged = DnaProcessor.Merge(spectrum);
            List<string> starters = new List<string>();
            if (wholeSpectrum)
                starters = spectrum;
            else
                foreach (var str in merged)
                {
                    starters.Add(str.Substring(0,partLen));
                }

            DnaGraph graph;
            if (positiveOnly)
                graph = new DnaGraphPosOnly();
            else if (negativeOnly)
                graph = new DnaGraphNegOnly();
            else
                graph = new DnaGraph();

            graph.StartFeromon = 0.0;
            graph.StartHeat = startHeat;
            graph.FeromoneToDistribute = feromoneToDistribute;

            graph.VaporRate = feromoneVaporRate;
            graph.HeatVaporRate = heatVaporRate;

            //Threshold bezużyteczny
            graph.Threshold = int.MaxValue;
            graph.TotalCost = seqLen - partLen;

            AntSystem ASRank;
            AntSystem.Ant result;

            graph.Initialize(spectrum);

            if (negativeOnly)
                ASRank = new NegAntSystem(ants_quant, graph, starters);
            else if (positiveOnly)
                ASRank = new PosAntSystem(ants_quant,graph,starters);
            else
                ASRank = new AntSystem(ants_quant, graph, starters);
            
            ASRank.Ranks = ranks_quant;
            
            result = ASRank.SolveAll(starters.Count * iteration_mult);

            QueryPerformanceCounter(ref stopTime);

            if(result == null)
            {
                Console.Error.WriteLine("Error. Check if given data is correct.");
                return;
            }

            string resultStr = AntSystem.AntToDNA(result);
            int used = 0;
            List<string> unusedNodes = new List<string>();
            foreach (string str in spectrum)
            {
                if (resultStr.Contains(str))
                    used++;
                else
                    unusedNodes.Add(str);
            }

            /*foreach (var str in unusedNodes)
            {
                Console.WriteLine(str);
            }
            List<string> merged_unused = DnaProcessor.Merge(unusedNodes);
            Console.WriteLine(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            foreach (var item in merged_unused)
            {
                Console.WriteLine(item);
            }
            Console.WriteLine(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            foreach (var item in DnaProcessor.Merge(spectrum))
            {
                Console.WriteLine(item);
            }
            Console.WriteLine(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");*/

            int optimal = seqLen - partLen + 1;
            string eq = "";
            if (positiveOnly)
            {                
                eq = "N-L+1";
            }
            else if (negativeOnly)
            {
                optimal = spectrum.Count;
                eq = "|S|";
            }
            else
            {
                optimal = Math.Min(optimal, spectrum.Count);
                eq = "Min(|S|, N-L+1)";
            }

            int quality = 100 * used / optimal ;


            string solution = AntSystem.AntToDNA(result).ToUpper();
            double time = (stopTime - startTime) * 1.0 / freq;

            if (!batch)
            {
                Console.WriteLine("Time elapsed: {0} seconds", time);

                Console.WriteLine("Best found: ");
                Console.WriteLine("\tUsed Nodes = {0}/{1} = {2}%", used, spectrum.Count, (used * 100) / spectrum.Count);
                Console.WriteLine("\tCost = " + result.SummaricCost);
                Console.WriteLine("\tQuality = used / " + eq + " = " + used + " / " + optimal + " = " + quality + "%");
                Console.WriteLine("\tLength = " + solution.Length);

                Program.WriteDna(solution);

                if (doBeep)
                    Console.Beep();
            }
            else
                Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}",time,used,spectrum.Count,solution.Length,quality);
            
        }

        public static void WriteDna(string dna)
        {
            for (int i = 0; i < dna.Length; i++)
            {
                char c = dna[i];
                switch (c)
                {
                    case 'a':
                    case 'A':    
                        Console.ForegroundColor = ConsoleColor.Red;
                        break;
                    case 'c':
                    case 'C':
                        Console.ForegroundColor = ConsoleColor.Yellow;
                        break;
                    case 'g':
                    case 'G':
                        Console.ForegroundColor = ConsoleColor.Green;
                        break;
                    case 't':
                    case 'T':
                        Console.ForegroundColor = ConsoleColor.Blue;
                        break;
                    default:
                        Console.ResetColor();
                        break;
                }
                Console.Write(c);
            }
            Console.ResetColor();
            Console.WriteLine();
        }
    }
}
